﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Windows.Media.Imaging;

namespace Pfz.Drawing.Wpf
{
	/// <summary>
	/// Class that helps loading all possible bitmap encoders and accessing them by the file extension.
	/// </summary>
	public sealed class BitmapEncoders
	{
		private Dictionary<string, BitmapEncoder> _encodersDictionary = new Dictionary<string, BitmapEncoder>();
		private List<BitmapEncoder> _encodersList = new List<BitmapEncoder>();

		/// <summary>
		/// Loads all encoders available in the current ApplicationDomain libraries.
		/// </summary>
		public void LoadAllDomainEncoders()
		{
			foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
			{
				try
				{
					LoadAllEncoders(assembly);
				}
				catch // we don't care the reason. If some encoder fails, we ignore it.
				{
				}
			}
		}

		/// <summary>
		/// Load all encoders available in a given library.
		/// </summary>
		public void LoadAllEncoders(Assembly assembly)
		{
			if (assembly == null)
				throw new ArgumentNullException("assembly");

			_allExtensions = null;

			Type[] types = assembly.GetTypes();
			foreach(var type in types)
			{
				if (!type.IsSubclassOf(typeof(BitmapEncoder)))
					continue;

				if (type.IsAbstract)
					continue;

				try
				{
					var objectEncoder = Activator.CreateInstance(type);
					var encoder = (BitmapEncoder)objectEncoder;
					LoadEncoder(encoder);
				}
				catch // again, if there is an error, we simple ignore the encoder.
				{
				}
			}
		}

		/// <summary>
		/// Loads the given encoder.
		/// </summary>
		public  void LoadEncoder(BitmapEncoder encoder)
		{
			if (encoder == null)
				throw new ArgumentNullException("encoder");

			if (_encodersList.Contains(encoder))
				return;

			var codecInfo = encoder.CodecInfo;
			string fileExtensionsString = codecInfo.FileExtensions.ToLowerInvariant();
			string[] fileExtensions = fileExtensionsString.Split(',');
			foreach (var fileExtension in fileExtensions)
			{
				BitmapEncoder otherEncoder;
				if (_encodersDictionary.TryGetValue(fileExtension, out otherEncoder))
				{
					if (codecInfo.Version > otherEncoder.CodecInfo.Version)
						_encodersDictionary[fileExtension] = encoder;
				}
				else
					_encodersDictionary.Add(fileExtension, encoder);
			}

			_encodersList.Add(encoder);
		}

		private string _allExtensions;
		/// <summary>
		/// Gets all the extensions supported by joining all formats supported by all encoders.
		/// </summary>
		public string AllExtensions
		{
			get
			{
				if (_allExtensions == null && _encodersDictionary.Count > 0)
				{
					StringBuilder result = new StringBuilder();
					result.Append("*");
					foreach(var extension in _encodersDictionary.Keys)
					{
						result.Append(extension);
						result.Append(";*");
					}

					result.Length -= 2;
					_allExtensions = result.ToString();
				}

				return _allExtensions;
			}
		}

		/// <summary>
		/// Gets the number of encoders available.
		/// </summary>
		public int EncoderCount
		{
			get
			{
				return _encodersList.Count;
			}
		}

		/// <summary>
		/// Gets the codec info for the given encoder index.
		/// </summary>
		/// <param name="encoderIndex"></param>
		/// <returns></returns>
		public BitmapCodecInfo GetCodecInfo(int encoderIndex)
		{
			return _encodersList[encoderIndex].CodecInfo;
		}

		/// <summary>
		/// Gets an empty encoder of the type of the encoder at encoderIndex.
		/// </summary>
		public BitmapEncoder GetEncoder(int encoderIndex)
		{
			var type = _encodersList[encoderIndex].GetType();
			object result = Activator.CreateInstance(type);
			return (BitmapEncoder)result;
		}

		/// <summary>
		/// Tries to get an encoder by an extension, or returns null.
		/// </summary>
		public BitmapEncoder TryGetEncoder(string extension)
		{
			if (extension == null)
				throw new ArgumentNullException("extension");

			BitmapEncoder result;
			if (!_encodersDictionary.TryGetValue(extension.ToLowerInvariant(), out result))
				return null;

			Type type = result.GetType();
			object untypedResult = Activator.CreateInstance(type);
			return (BitmapEncoder)untypedResult;
		}

		/// <summary>
		/// Gets an encoder by an extension or throws an exception.
		/// </summary>
		public BitmapEncoder GetEncoder(string extension)
		{
			BitmapEncoder result = TryGetEncoder(extension);
		
			if (result == null)
				throw new ArgumentException("Can't find an encoder for extension '" + extension + "'.");

			return result;
		}
	}
}
